#!/usr/bin/python
# encoding: utf-8
# description: get local ip address

import os
import traceback
import configparser
import subprocess
import netifaces
import hashlib
import jkzuc
import uuid
import re
import struct
import binascii
import ctypes
import sys
import importlib

# reload(sys)
importlib.reload(sys)
# sys.setdefaultencoding('utf-8')  # 只能在python2上运行

c = jkzuc.command()
s = jkzuc.stat()


class ModuleCtx(object):

    def __init__(self, logger_creator):
        """
        logger_creator创建logger实例
        """
        super(ModuleCtx, self).__init__()
        self.logger_creator = logger_creator  # 用于输出日志

    def logger(self):
        return self.logger_creator()


def read_robot_info(module_ctx):
    robot_id = ""
    robot_name = ""
    try:
        s.poll()
        serial_num = s.robot_serial_num

        prefix = ""
        serial = serial_num / 100000000
        ver_id = serial_num % 100000000
        content = ""
        if serial == 0:
            prefix = "Zu"
            robotModel = (serial_num % 100000000) / 10000
            if (
                robotModel == 313
                or robotModel == 510
                or robotModel == 511
                or robotModel == 729
                or robotModel == 730
                or robotModel == 1212
                or robotModel == 1812
            ):
                prefix = "C"
            elif robotModel == 515 or robotModel == 731 or robotModel == 1213 or robotModel == 1610 or robotModel == 1813:
                prefix = "Pro"
            content = str(ver_id)
        elif serial == 1:
            prefix = "MC"
            content = "%08d" % ver_id

        robot_id = prefix + content
        robot_name = s.robot_name
    except Exception:
        robot_name = "err"
        module_ctx.logger().error(traceback.format_exc())
    return robot_id, robot_name


# 获取ini配置文件filename中指定section中key的值
def read_ini_config(filename, section, key, fallback=None, module_ctx=None):
    value = ""
    try:
        config = configparser.ConfigParser()
        config.read(filename, encoding="utf-8")
        if fallback != None:
            value = config.get(section, key, fallback=fallback)
        else:
            value = config.get(section, key)
    except Exception:
        module_ctx.logger().error("[ERR] err in filename:{} node: {}.{}".format(filename, section, key))
        module_ctx.logger().error(traceback.format_exc())
    return value


# 检测当前机器人的作业模式
def check_robot_work_mode(inifile, module_ctx):
    work_file = "demo.ngc"
    try:
        # module_ctx.logger().debug("++++++++++++++++++++++++++++-------------------")
        # module_ctx.logger().debug(inifile)
        # module_ctx.logger().debug("++++++++++++++++++++++++++++-------------------")
        work_mode = read_ini_config(inifile, "TASK", "DEFAULT_ENABLE", fallback="0", module_ctx=module_ctx)
        # module_ctx.logger().debug("work_mode is: ", work_mode)
        work_file = read_ini_config(inifile, "TASK", "DEFAULT_PROG", fallback="/usr/etc/jkzuc/scripts/program/none/none.jks", module_ctx=module_ctx)
        # module_ctx.logger().debug("work_file is: ", work_file)
    except Exception:
        work_mode = 0
        work_file = ""

    if work_mode == "":
        work_mode = 0

    if int(work_mode) != 0:
        c.task_plan_synch()
        c.wait_complete()
        c.program_open(work_file)


def getMD5(arg):
    digest = hashlib.md5()
    digest.update(arg.encode())
    return digest.hexdigest()


def get_broad_addr(ipstr, maskstr):
    iptokens = map(int, ipstr.split("."))
    masktokens = map(int, maskstr.split("."))
    broadlist = []
    for i in range(len(iptokens)):
        ip = iptokens[i]
        mask = masktokens[i]
        broad = ip & mask | (~mask & 255)
        broadlist.append(broad)
    return ".".join(map(str, broadlist))


def get_primary_port_broadcast():
    routingIPAddr = ""
    broadcastIp = ""
    filename = "/etc/systemd/network/primary.network"
    name = read_ini_config(filename, "Match", "Name", module_ctx=module_ctx)
    if netifaces.interfaces().count(name) == 0:
        # may be in vmware.So name is ens33
        name = "ens33"
    if netifaces.interfaces().count(name) != 0:
        routingIPAddr = netifaces.ifaddresses(name)[netifaces.AF_INET][0]["addr"]
        routingIPNetmask = netifaces.ifaddresses(name)[netifaces.AF_INET][0]["netmask"]
        broadcastIp = get_broad_addr(routingIPAddr, routingIPNetmask)
    return routingIPAddr, broadcastIp


def get_all_broadcast():
    broadcastList = []
    for interface in netifaces.interfaces():
        # module_ctx.logger().debug(netifaces.ifaddresses(interface))
        try:
            routingIPAddr = netifaces.ifaddresses(interface)[netifaces.AF_INET][0]["addr"]
            routingIPNetmask = netifaces.ifaddresses(interface)[netifaces.AF_INET][0]["netmask"]
            # module_ctx.logger().debug("routingIpAddr", routingIPAddr)
            # module_ctx.logger().debug("routingIpNetMask", routingIPNetmask)
            broadcastIp = get_broad_addr(routingIPAddr, routingIPNetmask)
            broadcastList.append(broadcastIp)
        except KeyError:
            continue
    return broadcastList


def get_mac_addr():
    node = uuid.getnode()
    mac = uuid.UUID(int=node).hex[-12:]
    strMacAddr = "-".join(mac[i : i + 2] for i in range(0, len(mac), 2))
    return strMacAddr


def runCmdAsRoot(cmd_str, module_ctx):
    # the following commands are supported to be executed:
    #       shutils reboot				    -- /sbin/reboot
    #       shutils shutdown			    -- /sbin/shutdown -h now
    #       shutils settime timestring      -- date -s \"%s\" && /sbin/hwclock --systohc
    #       shutils install				    -- make install
    #       shutils sshstart			    -- start sshserver
    #       shutils sshstop				    -- stop sshserver & kill established connect
    #       shutils setsshclientip [ip]	    -- set ip accessable to ssh server
    #       shutils restartService  service_name       -- "systemctl restart \'%s\'"
    #       shutils disService  service_name           -- "systemctl disable \'%s\'"
    #       shutils stopService  service_name          -- "systemctl stop \'%s\'"
    # *********************************************************************************
    try:
        shutils = os.path.join(jkzuc.jkroot_dir, 'bin/shutils')
        subprocess.call(f'{shutils} {cmd_str}', shell=True)
    except Exception:
        module_ctx.logger().error(traceback.format_exc())


def getScbVerStr(stat):
    stat.poll()
    return stat.monitor_data[10], stat.monitor_data[1]


def getRcVerStr():
    return jkzuc.controller_version


def getServoVerStr(servoVer):
    verStr = "0.0"
    verNum = 0
    if servoVer:
        servo = hex(servoVer).replace("0x", "")
        """
        servo[0]:1 debug 2 release
        servo[1:4]: hardware version
        servo[4]:software branch 1:1.4 2:1.5 other:Customized customers
        servo[5:8]: software version
        """
        # module_ctx.logger().debug('servo version origin = ', servo)
        if servo[0] == "1":
            servo_version = "D"
        elif servo[0] == "2":
            servo_version = "R"
        elif servo[0] == "3":
            servo_version = "A"
        elif servo[0] == "4":
            servo_version = "B"
        elif servo[0] == "5":
            servo_version = "C"
        else:
            servo_version = "E"
        verStr = servo_version + "-" + servo[1] + "." + servo[2:4] + "_" + servo[4] + "." + servo[5:8]
        verNum = int(servo[4:8], 16)
    return verStr, verNum


def getTioVer(tioVer):
    """get Tio hardware info
    只能输入uint32，超出部分将被舍弃
    Args:
        tioVer: should be UINT32 type. data from CAN
        example:0x02480300
    Returns :
        list
            list[0]:str 类型 [固件版本]_[硬件版本] example:'2.48_3.00'
            list[1]:int 类型 固件版本 example: 0248
            list[2]:int 类型 硬件主版本 example: 3
    """
    tio_list = ["TIO-0.0_0.0", 0, 0]
    if tioVer != 0:
        tioVer = tioVer & 0xFFFFFFFF
        tio_list[0] = "TIO-{:x}.{:x}_{:x}.{:x}".format((tioVer >> 24) & 0x0F, (tioVer >> 16) & 0xFF, (tioVer >> 8) & 0xFF, tioVer & 0xFF)
        tio_list[1] = int("{:x}".format(tioVer >> 16))
        tio_list[2] = int("{:X}".format(tioVer >> 8 & 0xFF), 10)
    return tio_list


EMPTYSTRING = ""
# Base32 encoding/decoding must be done in Python
_b32alphabet = {
    0: "2",
    9: "7",
    18: "S",
    27: "v",
    1: "N",
    10: "K",
    19: "4",
    28: "C",
    2: "T",
    11: "e",
    20: "O",
    29: "h",
    3: "5",
    12: "m",
    21: "b",
    30: "L",
    4: "Y",
    13: "3",
    22: "W",
    31: "f",
    5: "j",
    14: "U",
    23: "x",
    6: "G",
    15: "P",
    24: "6",
    7: "d",
    16: "A",
    25: "Z",
    8: "I",
    17: "R",
    26: "Q",
}

_b32tab = _b32alphabet.items()
_b32tab = list(_b32tab)
_b32tab.sort()
_b32tab = [v for k, v in _b32tab]
_b32rev = dict([(v, int(k)) for k, v in _b32alphabet.items()])


def b32encode(s):
    """Encode a string using alike Base32,but not Base32.

    s is the string to encode.  The encoded string is returned.
    """
    parts = []
    quanta, leftover = divmod(len(s), 5)
    # Pad the last quantum with zero bits if necessary
    if leftover:
        s += "\0" * (5 - leftover)
        quanta += 1
    for i in range(quanta):
        # c1 and c2 are 16 bits wide, c3 is 8 bits wide.  The intent of this
        # code is to process the 40 bits in units of 5 bits.  So we take the 1
        # leftover bit of c1 and tack it onto c2.  Then we take the 2 leftover
        # bits of c2 and tack them onto c3.  The shifts and masks are intended
        # to give us values of exactly 5 bits in width.
        c1, c2, c3 = struct.unpack("!HHB", s[i * 5 : (i + 1) * 5])
        c2 += (c1 & 1) << 16  # 17 bits wide
        c3 += (c2 & 3) << 8  # 10 bits wide
        parts.extend(
            [
                _b32tab[c1 >> 11],  # bits 1 - 5
                _b32tab[(c1 >> 6) & 0x1F],  # bits 6 - 10
                _b32tab[(c1 >> 1) & 0x1F],  # bits 11 - 15
                _b32tab[c2 >> 12],  # bits 16 - 20 (1 - 5)
                _b32tab[(c2 >> 7) & 0x1F],  # bits 21 - 25 (6 - 10)
                _b32tab[(c2 >> 2) & 0x1F],  # bits 26 - 30 (11 - 15)
                _b32tab[c3 >> 5],  # bits 31 - 35 (1 - 5)
                _b32tab[c3 & 0x1F],  # bits 36 - 40 (1 - 5)
            ]
        )
    encoded = EMPTYSTRING.join(parts)
    # Adjust for any leftover partial quanta
    if leftover == 1:
        return encoded[:-6] + "aaaaaa"
    elif leftover == 2:
        return encoded[:-4] + "aaaa"
    elif leftover == 3:
        return encoded[:-3] + "aaa"
    elif leftover == 4:
        return encoded[:-1] + "a"
    return encoded


def b32decode(s):
    """Decode a Base32 encoded string.

    s is the string to decode.  Optional casefold is a flag specifying whether
    a lowercase alphabet is acceptable as input.  For security purposes, the
    default is False.

    The decoded string is returned.  A TypeError is raised if s were
    incorrectly padded or if there are non-alphabet characters present in the
    string.
    """
    quanta, leftover = divmod(len(s), 8)
    padchars = 0
    mo = re.search("(?P<pad>[a]*)$", s)
    if mo:
        padchars = len(mo.group("pad"))
        if padchars > 0:
            s = s[:-padchars]
    # Now decode the full quanta
    parts = []
    acc = 0
    shift = 35
    for c in s:
        val = _b32rev.get(c)
        if val is None:
            raise TypeError("Non-base32 digit found")
        acc += _b32rev[c] << shift
        shift -= 5
        if shift < 0:
            parts.append(binascii.unhexlify("%010x" % acc))
            acc = 0
            shift = 35
    # Process the last, partial quanta
    last = binascii.unhexlify("%010x" % acc)
    if padchars == 0:
        last = ""  # No characters
    elif padchars == 1:
        last = last[:-1]
    elif padchars == 3:
        last = last[:-2]
    elif padchars == 4:
        last = last[:-3]
    elif padchars == 6:
        last = last[:-4]
    else:
        raise TypeError("Incorrect padding")
    parts.append(last)
    return EMPTYSTRING.join(parts)


# input should be string YYYYMMDD
# output servovalue in32
def hexstr2decint(date):
    return int(date.upper(), 16)


# input should be servovalue int32
# output string YYYYMMDD
def decint2hexstr(dateint32):
    base = [str(x) for x in range(10)] + [chr(x) for x in range(ord("A"), ord("A") + 6)]
    num = dateint32
    mid = []
    while True:
        if num == 0:
            break
        num, rem = divmod(num, 16)
        mid.append(base[rem])
    return "".join([str(x) for x in mid[::-1]])


def sync_path(path, module_ctx):
    libc = ctypes.CDLL("libc.so.6")
    if os.path.isfile(path):
        fd = os.open(path, os.O_RDWR)
        libc.fsync(fd)
        os.close(fd)
    elif os.path.isdir(path):
        file_list = os.listdir(path)
        for file_name in file_list:
            file_path = os.path.join(path, file_name)
            if os.path.islink(file_path):
                module_ctx.logger().debug("[gRPC] file link {} is jumped.".format(file_path))
            elif os.path.isfile(file_path):
                fd = os.open(file_path, os.O_RDWR)
                libc.fsync(fd)
                os.close(fd)
                module_ctx.logger().debug("[gRPC] {} is synced to disk, size: {}".format(file_path, os.path.getsize(file_path)))
            elif os.path.isdir(file_path):
                sync_path(file_path, module_ctx)
                module_ctx.logger().debug("[gRPC] subdir {} is synced to disk".format(file_path))
